package horncomb.bpm.process.def;

import horncomb.bpm.BpmContextHolder;
import horncomb.bpm.process.core.EnterableState;
import horncomb.bpm.process.core.ExecutionContext;
import horncomb.bpm.process.core.LeavableState;
import horncomb.bpm.process.core.ProcessDefinition;
import horncomb.bpm.process.core.StateDefinition;
import horncomb.bpm.process.core.Transition;

import java.io.Serializable;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * @author Daniel
 */
public class DefaultTransition implements Serializable, Transition {
	private static final long serialVersionUID = 1331712764028118353L;
	private String processDefinitionId;
	transient private ProcessDefinition processDefinition;
	private String id;
	private String name;

	private String sourceStateDefinitionId;
	private String targetStateDefinitionId;

	private Action action;

	public DefaultTransition() {
		super();
	}

	public DefaultTransition(String name) {
		super();
		this.name = name;
	}

	/**
	 * {@inheritDoc}
	 */
	public LeavableState getSourceState() {
		Assert.hasText(this.sourceStateDefinitionId);
		StateDefinition stateDef = this.getProcessDefinition()
				.findStateDefinition(this.sourceStateDefinitionId);
		if (!(stateDef instanceof LeavableState)) {
			// TODO StateTypeMisMatchException
		}
		return (LeavableState) stateDef;
	}

	/**
	 * {@inheritDoc}
	 */
	public EnterableState getTargetState() {
		Assert.hasText(this.targetStateDefinitionId);
		StateDefinition stateDef = this.getProcessDefinition()
				.findStateDefinition(this.targetStateDefinitionId);
		if (!(stateDef instanceof EnterableState)) {
			// TODO StateTypeMisMatchException
		}
		return (EnterableState) stateDef;
	}

	public boolean checkBeforeTrasint(ExecutionContext context) {
		return this.getSourceState().equals(
				context.getToken().getState());
	}

	/**
	 * {@inheritDoc}
	 */
	public void transit(ExecutionContext context) {
		if (!this.checkBeforeTrasint(context)) {
			throw new TansitionException("State cannot be transiting.",
					context.getToken(), this, context
							.getToken().getState());
		}
		this.doTransit(context);
	}

	protected void doTransit(ExecutionContext context) {
		context.setTransition(this);
		this.getSourceState().leave(context);
		this.doAction(context);
		context.setTransition(null);
		this.getTargetState().enter(context);
	}

	protected void doAction(ExecutionContext context) {
		if (this.action != null) {
			this.action.execute(context);
		}
	}

	/**
	 * @return the id
	 */
	public String getId() {
		if (!StringUtils.hasText(id)) {
			this.id = this.processDefinitionId + ":"
					+ this.sourceStateDefinitionId + "->"
					+ this.targetStateDefinitionId;
		}
		return id;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(String id) {
		this.id = id;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return StringUtils.hasText(name) ? name : this.getId();
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the sourceStateId
	 */
	public String getSourceStateDefinitionId() {
		return sourceStateDefinitionId;
	}

	/**
	 * @param sourceStateId
	 *            the sourceStateId to set
	 */
	public void setSourceStateDefinitionId(String sourceStateId) {
		this.sourceStateDefinitionId = sourceStateId;
	}

	/**
	 * @return the targetStateId
	 */
	public String getTargetStateDefinitionId() {
		return targetStateDefinitionId;
	}

	/**
	 * @param targetStateId
	 *            the targetStateId to set
	 */
	public void setTargetStateDefinitionId(String targetStateId) {
		this.targetStateDefinitionId = targetStateId;
	}

	/**
	 * @return the processDefinition
	 */
	public ProcessDefinition getProcessDefinition() {
		if (processDefinition == null) {
			this.processDefinition = BpmContextHolder.getContext()
					.getProcessManagementService().findProcessDefinition(
							this.processDefinitionId);
		}
		return this.processDefinition;
	}

	/**
	 * @param processDefinition
	 *            the processDefinition to set
	 */
	public void setProcessDefinition(ProcessDefinition processDefinition) {
		this.processDefinition = processDefinition;
		this.processDefinitionId = processDefinition.getId();
	}

	/**
	 * @return the processDefinitionId
	 */
	public String getProcessDefinitionId() {
		return processDefinitionId;
	}

	/**
	 * @param processDefinitionId
	 *            the processDefinitionId to set
	 */
	public void setProcessDefinitionId(String processDefinitionId) {
		this.processDefinitionId = processDefinitionId;
	}

	/**
	 * @param action the action to set
	 */
	public void setAction(Action action) {
		this.action = action;
	}
}
